// $Id: nodevanilla.cpp 69 2004-11-18 10:18:04Z jonas $
#include "nodevanilla.h"
#include "loghandler.h"
//#include <stdlib.h>	// For abs()
#include <algorithm>

#include <iostream>
using namespace std;

#include "clock.h"

/**
 * Init all members.
 *
 * @param bandwidthUp Upload bandwidth in bytes per timestep (1 time step is in
 *  seconds and can be obtained from Clock::m_secondsPerTimeStep)
 * @param bandwidthDown Download bandwidth in bytes per timestep.
 * @param routingTableSize Number of entries in the routing table.
 * @param fileStoreSize Number of files to fit in the file store.
 * @param requestHopsToLive The HTL to use on requests.
 * @param insertHopsToLive The HTL to use on inserts.
 * @param requestProbability The probability for one node to request a file at
 *  one time step.
 * @param insertProbability The probability for one node to insert a file at
 *  one time step.
 * @param fileHandler A pointer to the file handler to use.
 */
NodeVanilla::NodeVanilla(unsigned int bandwidthUp, unsigned int bandwidthDown, unsigned int routingTableSize, unsigned int fileStoreSize, unsigned int requestHopsToLive, unsigned int insertHopsToLive, float requestProbability, float insertProbability, FileHandler *fileHandler)
	: Node(bandwidthUp, bandwidthDown, routingTableSize, fileStoreSize, requestHopsToLive, insertHopsToLive, requestProbability, insertProbability, fileHandler)
{
}

/**
 * There is nothing to free or unset here...
 */
NodeVanilla::~NodeVanilla()
{
}

/**
 * Adds a neighbor to the noded routing table. Implements LRU (Least Recently
 *  Used) cache scheme.
 *
 * @return Returns true if the node was added to the list.
 */
bool NodeVanilla::addNeighbor(Node *neighbor, unsigned int hash)
{
	// We don't want ourselves as neighbors!!!
	if (neighbor == ((Node *)this)) {
		return false;
	}

	//if (find(m_routingTable.begin(), m_routingTable.end(), neighbor) == m_routingTable.end()) {
	// Only insert if hash is uniqe
	list<RoutingEntry>::iterator entry = find(m_routingTable.begin(), m_routingTable.end(), RoutingEntry(0, hash));
	if (entry == m_routingTable.end()) {
		// TODO: Optimize this insert by sorting by Id or something? Or just general optimization? How?
		m_routingTable.push_back(RoutingEntry(neighbor, hash));

		if (m_routingTable.size() > m_routingTableSize) {
			// Cache is full. Remove LRU neighbor
// cout << "NodeVanilla::addNeighbor() >> Routing cache full. I removed nodeId: " << (*m_routingTable.begin())->getId() << endl;
			m_routingTable.erase(m_routingTable.begin());
		}

		return true;
	}
	else {
		(*entry).m_node = neighbor;
		return true;
	}
}

/**
 * This is the main part of the node. It whats make the simulation tick. Here
 * each node decides if they shall insert and/or request files. And they
 * clense the list of active transfers, removing those that are completed.
 */
void NodeVanilla::execute()
{
//cout << "NodeVanilla::execute()" << endl;
	if (doRequest()) {
		File *file = m_fileHandler->getFile(((Node *)this));
		// Only request a file if there is one...
		if (file) {
			((Node *) this)->requestFile(file, m_requestHopsToLive);
//			this->requestFile(file, m_requestHopsToLive);
/*cout << "NodeVanilla::execute(" << ((Node *)this) << ") >> I requested a file!" << endl;
		}
		else {
cout << "NodeVanilla::execute(" << ((Node *)this) << ") >> I did NOT request a file!" << endl;*/
		}
	}

	if (doInsert()) {
		((Node *) this)->insertFile(m_fileHandler->createFile(((Node *)this)), m_insertHopsToLive);
// cout << "NodeVanilla::execute(" << ((Node *)this) << ") >> I inserted a file!" << endl;
	}

	// Remove all finished transfers!!!
	for (vector<Transfer *>::iterator transfer=m_transfers.begin(); transfer!=m_transfers.end(); ) {
		//if ((*transfer)->m_progress >= (*transfer)->m_file->m_file->m_size) {
		//if (((Node *) (*transfer)->m_destination)->getStoredFile((File *) (*transfer)->m_file->m_file)->m_size >= (*transfer)->m_file->m_file->m_size) {
		//Node *destination = ((Node *) (*transfer)->m_destination);
		File *file = (File *) (*transfer)->m_file->m_file;
		StoredFile *storedFile = ((Node *) (*transfer)->m_destination)->getStoredFile(file);
// assert(storedFile);

		if (!storedFile || (storedFile->m_size >= file->m_size)) {
			// Call LogHandler and tell about a successful transfer?
			LogHandler::getInstance()->node_transfer(((Node *)this), *transfer, 2);
/*cout << "NodeVanilla::execute(" << ((Node *)this) << ") >> Transfer finished!" << endl;
cout << "  file hash: " << (*transfer)->m_file->m_file->m_hash << endl;
cout << "  file size: " << (*transfer)->m_file->m_file->m_size << endl;
cout << "  current ts: " << Clock::getInstance()->getTime() << endl;*/

			// Transfer is complete! Remove it from the list.
			delete (*transfer);
			m_transfers.erase(transfer);
		}
		else {
			transfer++;
		}
/*cout << "NodeVanilla::execute(" << getId() << ") >> node: " << tmpNode << ", file: " << tmpFile << ", stored: " << tmpStored << endl;
cout << "NodeVanilla::execute(" << getId() << ") >> nodeId: " << tmpNode->getId() << ", fileId: " << tmpFile->m_hash << ", storedId: " << tmpStored->m_file->m_hash << endl;*/
	}

	// Clear the list of recognized search queries (I love simulating!!!)
	m_processedGuids.clear();
}

/**
 * Store a file on this node. If the file store is full it will delete the
 *  Least Recently Used file in the filestore.
 *
 * @param file The file to store
 * @param fullSize Do we wish to store the file with its full size (or create
 *  an empty file (false))? Default is false = empty file.
 * @returns A pointer to the newly created StoredFile object
 */
StoredFile *NodeVanilla::storeFile(File *file, bool fullSize)
{
	//StoredFile *newFile = new StoredFile(file, file->m_size);
	StoredFile *newFile;
	if (fullSize) {
	 	newFile = new StoredFile(file, file->m_size);
	}
	else {
	 	newFile = new StoredFile(file);
	}

	// Make sure we don't add the same file again...
	map<File *, StoredFile *>::iterator iter = m_fileStore.find(file);
	if (iter != m_fileStore.end()) {
cout << "NodeVanilla::storeFile(" << ((Node *)this) << ") >> Tried to add same file again! file hash: " << file->m_hash << endl;
		return (*iter).second;
	}

	// Remove an old file if the file store is full
	if (m_fileStore.size() >= m_fileStoreSize) {
		// Find LRU file
		map<File *, StoredFile *>::iterator oldestFile = m_fileStore.begin(), currentFile = oldestFile;
		for (currentFile++; currentFile!=m_fileStore.end(); currentFile++) {
			if ((*currentFile).second->m_timeLastUsed < (*oldestFile).second->m_timeLastUsed) {
				oldestFile = currentFile;
			}
		}

		// Delete object and erase iterator
		delete (*oldestFile).second;
		m_fileStore.erase(oldestFile);
	}

	// Add the file to the file store
	m_fileStore[file] = newFile;
	return newFile;
}

/**
 * This method is used by standard freenet to search for a file in the network
 * by recursing through its neighbor nodes requestFile-methods.
 * @todo Implement freenet style distance!
 *
 * @param params A list of parameters. See NodeRequestFileParameters for more
 *  info on each parameter.
 * @returns True is returned if the file is found on this node (or another) and
 *  a transfer will be set up. False is returned only if all neighbors fail to
 *  locate the file.
 */
//bool NodeVanilla::requestFile(unsigned int guid, Node *requestor, File *file, int &hopsToLive)
bool NodeVanilla::routeFile(Node *requestor, NodeRequestFileParameters *params)
{
//cout << "NodeVanilla::routeFile(" << ((Node *)this) << ") >> Routing request, file hash: " << params->m_file->m_hash << endl;
	// Don't accept if guid is already seen. Decrease htl anyway?)
	map<unsigned int, bool>::iterator findGuid = m_processedGuids.find(params->m_guid);
	//if (m_processedGuids[params->m_guid]) {
	if (findGuid != m_processedGuids.end()) {
		// We have already seen this request, return false!
		LogHandler::getInstance()->node_routeFile(((Node *) this), requestor, params, 2);
// cout << "  request seen before." << endl;
		return false;
	}
	m_processedGuids[params->m_guid] = true;

	// Decrease the htl counter
	if (--params->m_hopsToLive < 0) {
		// TODO: This should never happen! Panic if it does?
		//return false;
		exit(-1);
	}
	params->m_hopCount++;

	// Look if file is in our datastore
	StoredFile *storedFile = getStoredFile(params->m_file);

	// We have the requested file!
	// Insert: This seems to hold for both!!!
	if (storedFile) {
		// Set up transfer to requestor (unless we are requesting it)
		if (requestor != ((Node *)this)) {
			Transfer *newTransfer = new Transfer(requestor, storedFile);
			// This transfer is the root of the transfer chain that will be set up
			newTransfer->m_isRootTransfer = true;
			m_transfers.push_back(newTransfer);
// cout << "  Transfer set up ___" << endl;
			LogHandler::getInstance()->node_transfer(((Node *)this), newTransfer, 0);
// cout << "                  ---" << endl;
		}
// cout << "NodeVanilla::routeFile(" << getId() << ") >> I have the file!" << endl;
		// Add ourselves as the files owner
		params->m_fileOwner = ((Node *)this);

		// Report success
// cout << "NodeVanilla::requestFile(" << getId() << ") >> New transfer! (I got it)" << endl;
		LogHandler::getInstance()->node_routeFile(((Node *) this), requestor, params, 0);
		return true;
	}

	// Test: We did not have the file, but before we consult our neighbors, lets make sure that we can afford one more transfer. If too many transfers are in progress we might have to delete a file while we are transfering, and that is not desirable.
	// Insert: Same here...
	if (m_fileStore.size() >= m_fileStoreSize) {
		// Our datastore is full. How about the transfers?
		// If the LRU file is not in a transfer, we can proceed. Find the LRU file
		// TODO: Make method (if we want to change cacheing...
		map<File *, StoredFile *>::iterator oldestFile = m_fileStore.begin(), currentFile = oldestFile;
		for (currentFile++; currentFile!=m_fileStore.end(); currentFile++) {
			if ((*currentFile).second->m_timeLastUsed < (*oldestFile).second->m_timeLastUsed) {
				oldestFile = currentFile;
			}
		}

		// If the file is not complete yet, we know for sure that it is transferring.
		if ((*oldestFile).second->m_size < (*oldestFile).second->m_file->m_size) {
			LogHandler::getInstance()->node_routeFile(((Node *) this), requestor, params, 4);
			return false;
		}

		// Find LRU file in transfers (with this node being the sender)
		for (vector<Transfer *>::iterator transfer=m_transfers.begin(); transfer!=m_transfers.end(); transfer++) {
			// Does this transfer work on the LRU stored file?
			if ((*transfer)->m_file == (*oldestFile).second) {
				// The LRU stored file is being transferred. We must abort this request in order to not have to abort a transfer.
				LogHandler::getInstance()->node_routeFile(((Node *) this), requestor, params, 4);
				return false;
			}
		}
	}
	// Phew, this request passed this mighty screening test. Lets hope the file can be found ;-)

	// We are inserting a file and it did not genereate any conflict!
	// Insert: (on htl==0) Create StoredFile and return
	if (params->m_insert && !params->m_hopsToLive) {
		// Create StoredFile entry
		storeFile(params->m_file);

		// Add file inserter to routing table
		addNeighbor(params->m_fileOwner, params->m_file->m_hash);

		// Pretend to be the owner. Helps to distinguish between a false (file not found) and a false (don't use me)
		//params->m_fileOwner = ((Node *)this);
		params->m_insertComplete = true;

		// Call LogHandler
		LogHandler::getInstance()->node_routeFile(((Node *) this), requestor, params, 1);

		// False as in "The file was not found"
		return false;
	}

	// Build list of compared ids and sort it
	list<KeyDistanceHelper> neighborDistance;
	generateRequestOrderList(params->m_file, neighborDistance);

	// Ask neighbors one by one, abort on hopsToLive==0
	for (list<KeyDistanceHelper>::iterator node=neighborDistance.begin(); node!=neighborDistance.end(); node++) {
		if (params->m_hopsToLive > 0) {
			// "Touch" neighbor. (That is remove it from m_routingTable and add it at the back again
			//list<Node *>::iterator touchNode = find(m_routingTable.begin(), m_routingTable.end(), (*node).m_node);
			list<RoutingEntry>::iterator touchNode = find(m_routingTable.begin(), m_routingTable.end(), RoutingEntry(0, (*node).m_entry->m_hash));
			if (touchNode != m_routingTable.end()) {
				m_routingTable.erase(touchNode);
				m_routingTable.push_back(*touchNode);
			} else {
				// TODO: This should never happen!!!
			}

			// We have htl to go... Ask away! (Don't care if not found, just loop and try next)
// cout << "NodeVanilla::routeFile(" << getId() << ") >> New request to nodeId: " << (*node).m_node->getId() << endl;
// cout << "  Asking node: " << (*node).m_entry->m_node << endl;
			bool fileFound = (*node).m_entry->m_node->routeFile(((Node *)this), params);
			// The file was found. Return it the same path it came from (applies to both request and insert)
			if (fileFound) {
				// Create StoredFile entry
				StoredFile *newFile = storeFile(params->m_file);

				// Set up transfer to requestor (unless we are the requestor of the file)
				if (((Node *)this) != requestor) {
					Transfer *newTransfer = new Transfer(requestor, newFile);
					m_transfers.push_back(newTransfer);

					// Add the files owner to our routing list.
					addNeighbor(params->m_fileOwner, params->m_file->m_hash);

// cout << "NodeVanilla::routeFile(" << getId() << ") >> New transfer! (Neighbor got it)" << endl;
					// Call LogHandler and tell about the new transfer
// cout << "  Transfer set up2 ___" << endl;
					LogHandler::getInstance()->node_transfer(((Node *)this), newTransfer, 0);
// cout << "                   ---" << endl;
				}
				else {
// cout << "NodeVanilla::routeFile(" << getId() << ") >> NO transfer! (transfer to self)" << endl;
				}

				// TODO: Report another fileOwner with some random value...

				// Report sucess
				LogHandler::getInstance()->node_routeFile(((Node *) this), requestor, params, 0);
				return true;
			}
			// We are inserting a file and got a gogogo command!
			//else if (params->m_insert && params->m_fileOwner) {
			else if (params->m_insertComplete) {
				// Create StoredFile entry
				StoredFile *newFile;
				if (requestor == ((Node *)this)) {
					// We are the ones inserting the file... We should have the complete file for starters
					newFile = storeFile(params->m_file, true);
				}
				else {
					// If we are waiting for the file it should be created as an empry file
					newFile = storeFile(params->m_file);
				}

				// Create transfer to the node that just returned
				Transfer *newTransfer = new Transfer((*node).m_entry->m_node, newFile);
				// This transfer is the root of the transfer chain that will be set up
				if (requestor == ((Node *)this)) {
					newTransfer->m_isRootTransfer = true;
				}
				m_transfers.push_back(newTransfer);

				// Call LogHandler and tell about the new transfer
// cout << "  Transfer set up (insert)! ___" << endl;
				LogHandler::getInstance()->node_transfer(((Node *)this), newTransfer, 1);
// cout << "                            ---" << endl;

				// Call LogHandler about the request!
				LogHandler::getInstance()->node_routeFile(((Node *) this), requestor, params, 1);
				return false;
			}
			// Try another neighbor...
			else {
// cout << "NodeVanilla::routeFile(" << getId() << ") >> False returned from nodeId: " << (*node).m_node->getId() << endl;
			}
		}
		else {
			// Hops to live ran out
			LogHandler::getInstance()->node_routeFile(((Node *) this), requestor, params, 1);
			return false;
		}
	}

	// We don't have the file and the neighbors (at depth hopsToLive) didn't have it!
	LogHandler::getInstance()->node_routeFile(((Node *) this), requestor, params, 3);
	return false;
}

/**
 * Fill the empty list (from argument) with all neighbors by putting the
 * neighbor that most probably can satisfy a request for the file in the best way.
 *
 * @param file Sort the list with respect to this file.
 * @param list An empty list which will become an ordered list of neighbors.
 */
void NodeVanilla::generateRequestOrderList(File *file, list<KeyDistanceHelper> &list)
{
	int distance;
	for (std::list<RoutingEntry>::iterator entry=m_routingTable.begin(); entry!=m_routingTable.end(); entry++) {
		//if ((*entry).m_node != requestor) {	// We don't want the requestor to get its own request, do we?
			// Calc freenet style distance
			distance = ::abs((int) (*entry).m_hash - file->m_hash);
			list.push_back(KeyDistanceHelper(distance, &(*entry)));
		//}
	}
	list.sort(SortKeyDistanceHelper());
}
